package com.gitee.qdbp.general.common.biz.settings.service;

import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Vector;
import org.apache.poi.ss.usermodel.Workbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.alibaba.fastjson.util.TypeUtils;
import com.gitee.qdbp.able.exception.ServiceException;
import com.gitee.qdbp.able.jdbc.ordering.OrderPaging;
import com.gitee.qdbp.able.jdbc.paging.PageList;
import com.gitee.qdbp.able.result.IBatchResult;
import com.gitee.qdbp.able.result.ResultCode;
import com.gitee.qdbp.base.system.model.IAccount;
import com.gitee.qdbp.general.common.api.settings.model.AreaBean;
import com.gitee.qdbp.general.common.api.settings.model.AreaChangedDetails;
import com.gitee.qdbp.general.common.api.settings.model.AreaDivisionBean;
import com.gitee.qdbp.general.common.api.settings.model.AreaDivisionWhere;
import com.gitee.qdbp.general.common.api.settings.service.IAreaDivisionBatch;
import com.gitee.qdbp.tools.excel.ImportCallback;
import com.gitee.qdbp.tools.excel.XExcelExporter;
import com.gitee.qdbp.tools.excel.XExcelParser;
import com.gitee.qdbp.tools.excel.XMetadata;
import com.gitee.qdbp.tools.excel.model.RowInfo;
import com.gitee.qdbp.tools.excel.utils.MetadataTools;
import com.gitee.qdbp.tools.files.PathTools;
import com.gitee.qdbp.tools.utils.PropertyTools;
import com.gitee.qdbp.tools.utils.VerifyTools;

/**
 * 行政区划批量操作处理类
 *
 * @author zhaohuihua
 * @version 170702
 */
@Service
public class AreaDivisionBatch implements IAreaDivisionBatch {

    private static final Logger log = LoggerFactory.getLogger(AreaDivisionBatch.class);

    private static String PROPERTIES = "settings/excel/area-division.txt";
    private static String TEMPLATE = "assets/excel/area-division.xlsx";

    private XMetadata metadata;

    private int importBufferSize = 20;

    @Autowired
    private AreaDivisionQueryer areaDivisionQueryer;
    @Autowired
    private AreaDivisionExecutor areaDivisionExecutor;

    @Override
    @Transactional(propagation = Propagation.NOT_SUPPORTED) // 批量操作, 失败不需要全部回滚
    public List<AreaChangedDetails> sync(String scene, IAccount me) throws ServiceException {
        List<AreaChangedDetails> changed = areaDivisionExecutor.compare(scene, me);
        areaDivisionExecutor.saveChanged(scene, changed, me);
        return changed;
    }

    @Override
    @Transactional(readOnly = true)
    public void exports(AreaDivisionWhere where, OrderPaging paging, String webroot, OutputStream output)
            throws ServiceException {

        if (metadata == null) {
            Properties properties = PropertyTools.load(PROPERTIES);
            metadata = MetadataTools.parseProperties(properties);
        }

        PageList<AreaDivisionBean> pageList = areaDivisionQueryer.list(where, paging);
        XExcelExporter exporter = new XExcelExporter(metadata);
        exporter.export(pageList.toList(), PathTools.concat(webroot, TEMPLATE), output);
    }

    @Override
    @Transactional(propagation = Propagation.NOT_SUPPORTED) // 批量操作, 失败不需要全部回滚
    public IBatchResult imports(String scene, InputStream is, IAccount me) throws ServiceException {
        if (VerifyTools.isAnyBlank(is)) {
            throw new ServiceException(ResultCode.PARAMETER_IS_REQUIRED);
        }

        if (metadata == null) {
            Properties properties = PropertyTools.load(PROPERTIES);
            metadata = MetadataTools.parseProperties(properties);
        }

        // 先清空全部数据再导入(也就是说每次导入都需要提交全量数据)
        areaDivisionExecutor.clear(scene, me);

        XExcelParser parser = new XExcelParser(metadata);
        Callback callback = new Callback(scene, me);
        parser.parse(is, callback);
        return callback;
    }

    private class Callback extends ImportCallback {

        private static final long serialVersionUID = 1L;
        private String scene;
        private IAccount me;

        private List<Item> buffer;

        public Callback(String scene, IAccount me) {
            this.scene = scene;
            this.me = me;
            this.buffer = new Vector<>();
        }

        @Override
        public void callback(Map<String, Object> map, RowInfo rowInfo) throws ServiceException {
            AreaBean bean = TypeUtils.castToJavaBean(map, AreaBean.class);

            synchronized (buffer) {
                buffer.add(new Item(bean, rowInfo));
            }
            if (buffer.size() >= importBufferSize) {
                this.flush();
            }
        }

        @Override
        public void finish(Workbook workbook, XMetadata metadata) {
            this.flush();
        }

        /** 这里是批量操作, 需要在方法内将所有异常处理掉, 不能再向上抛异常了, 因为上层无法知道具体是哪一行发生了错误 **/
        private void flush() {
            // 批量入库
            List<Item> items = new ArrayList<>();
            synchronized (buffer) {
                if (buffer.isEmpty()) {
                    return;
                }
                items.addAll(buffer);
                buffer.clear();
            }
            try {
                List<AreaBean> list = new ArrayList<>();
                for (Item item : items) {
                    list.add(item.model);
                }
                areaDivisionExecutor.imports(scene, list, me);
            } catch (Throwable t) {
                // 一旦操作失败, 则单条重试
                for (Item item : items) {
                    try {
                        areaDivisionExecutor.imports(scene, Arrays.asList(item.model), me);
                    } catch (ServiceException e) {
                        this.addFailed(item.info.getSheetName(), item.info.getRow(), e);
                        if (e.getCause() != null) {
                            log.error("excel import error, " + e.getMessage() + ", " + e.getCause().getMessage());
                        }
                    } catch (Throwable e) {
                        this.addFailed(item.info.getSheetName(), item.info.getRow(), ResultCode.SERVER_INNER_ERROR);
                        if (e.getCause() == null) {
                            log.error("excel import error, " + e.getMessage());
                        } else {
                            log.error("excel import error, " + e.getMessage() + ", " + e.getCause().getMessage());
                        }
                    }
                }
            }
            areaDivisionExecutor.clearCache(scene, me);
        }

    }

    private static class Item {

        AreaBean model;
        RowInfo info;

        public Item(AreaBean model, RowInfo info) {
            this.model = model;
            this.info = info;
        }
    }

}
